--[[ 武装飞鸟脚本,作用是显在坐标位置显示一个方块和十字辅助线,还有显示诸如火力之类的数据
  可作为彩京等stg游戏的模版
  
  更新发现,其实命中判定的左右上下边缘都是直接记录的!不存在中心点坐标的记录!
  这下好了方块大小固定,不用再测试和大概目测了;
  增加的错误预防,一旦发现运行时错误将记录到ERROR_FLAG,以后每帧绑定的函数直接return不再运行;
  增加了按帧执行的任务表,增加了防多次加载的代码(按F4重启会多次加载,在print()时才发现的,已做了预防);
  实测发现不能使用read_u16读取,在cheatFinder中不能用big U16搜索
  
  轻度改装到MyMame模块,仅用防错每帧运行
  添加最大火力剩余数字,2字节但不能用cheatFinder找也不能read_u16
  
  By setycyas @2025-02-11
]]

--防止重复运行
if (_G.game) then
  print('script already loaded!')
  return
end

local myMame = require('MyMame')

-- 这里设定基本参数
local GAME = {
  -- 游戏名,防重加载用
  NAME = "gunbird_autoboot",
  -- 防错,一旦运行出错立即设定为true,在main()函数中使用,可避免绑定每帧运行后不断出错
  ERROR_FLAG = false,
  -- 分辨率
  X_RESOLUTION = 224,
  Y_RESOLUTION = 320,
  -- 横坐标只需要低位,纵坐标需要高低位,无法用读取双字节的方法直接读数据
  XLEFT_ADDR_LOW = 0x00fe0339,
  XRIGHT_ADDR_LOW = 0x00fe033d,
  YDOWN_ADDR_LOW = 0x00fe033b,
  YDOWN_ADDR_HIGH = 0x00fe033b-1,
  YUP_ADDR_LOW = 0x00fe033f,
  YUP_ADDR_HIGH = 0x00fe033f-1,
  -- 像素平移
  --X_OFFSET = 5,
  --Y_OFFSET = 5,
  -- 火力地址
  POW_ADDR = 0x00fe032f,
  POW_REMAIN_ADDR_LOW = 0x00fe031d,
  POW_REMAIN_ADDR_HIGH = 0x00fe031d-1,
  -- 显示方块的半长半高,单位为MAME的屏幕比例,考虑到实际是像素为单位,定义时用像素除以分辨率的形式
  --BOX_HALF_WIDTH = 4.0/224,
  --BOX_HALF_HEIGHT = 7.0/320,
  -- 中心方块的边框颜色和中心颜色
  BOX_BORDER_COLOR = 0x8800ff00,
  BOX_COLOR = 0x5500ff00,
  --大范围十字线辅助
  -- 中心十字线与边缘参考线的颜色
  --CENTER_LINE_COLOR = 0x88ff0000,
  SIDE_LINE_COLOR = 0xFFffffff,
  SIDE_LINE_FULL_COLOR = 0xFFff0000, -- 满能量时的颜色
  -- 中途需要变更的测试参数,准备了10个
  TEST_PARAMS = {0x00fe032f+5, 64.0, 208.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
  -- mame api用的对象,在refreshDevices()函数刷新获取
  mem = nil,
  container = nil,
  ui_container = nil,
  -- 特定帧任务,只记录按下,按键时间至少相隔5帧以保证上一个键有时间放开
  frame_missions = { --此任务可选女悟空并选关:街道-工厂-城堡
    [200] = {fieldName = "Coin 1", value = 1},
    [270] = {fieldName = "1 Player Start", value = 1},
    [350] = {fieldName = "P1 Right", value = 1},
    [420] = {fieldName = "P1 Right", value = 1},
    [499] = {fieldName = "P1 Button 1", value = 1}
  }
}

-- 完成任务指令,每完成一个按键指令添加一个3帧后的放开指令
function GAME.doMissions()
  local field, frame_number, mission
  frame_number = manager.machine.screens[":screen"]:frame_number()
  -- 最大帧数保护,超过直接返回提高运行效率
  if frame_number > 1000 then
    return
  end
  -- 执行任务
  mission = GAME.frame_missions[frame_number]
  if mission then
    -- 对于按下键的情况,添加放开键的操作
    if mission.value > 0 then
      GAME.frame_missions[frame_number+3] = {fieldName = mission.fieldName, value = 0}
    end
    -- 执行操作
    field = manager.machine.ioport.ports[":P1_P2"].fields[mission.fieldName]
    field:set_value(mission.value)
    print("mission "..frame_number.." finished!")
  end
end

-- 刷新设备,成功获取设备返回true,否返回false
function GAME.refreshDevices()
  GAME.mem = manager.machine.devices[':maincpu'].spaces['program']
  GAME.container = manager.machine.screens[":screen"].container
  GAME.ui_container = manager.machine.render.ui_container
  
  if (GAME.mem and GAME.container and GAME.ui_container) then
    return true
  else
    return false
  end
end

-- 坐标变换函数,从得到的坐标变为屏幕显示位置(是个比例),返回0.0-1.0的浮点数,
-- 由于是两侧坐标,返回两个值
function GAME.getX()
  local xleft, xright
  xleft = 1.0*GAME.mem:read_u8(GAME.XLEFT_ADDR_LOW)/GAME.X_RESOLUTION
  xright = 1.0*GAME.mem:read_u8(GAME.XRIGHT_ADDR_LOW)/GAME.X_RESOLUTION
  return xleft, xright
end
function GAME.getY()
  local ydown, yup
  ydown = 1.0*(GAME.mem:read_u8(GAME.YDOWN_ADDR_LOW)+GAME.mem:read_u8(GAME.YDOWN_ADDR_HIGH)*256)/GAME.Y_RESOLUTION
  yup = 1.0*(GAME.mem:read_u8(GAME.YUP_ADDR_LOW)+GAME.mem:read_u8(GAME.YUP_ADDR_HIGH)*256)/GAME.Y_RESOLUTION
  return ydown, yup
end

-- 数据显示
function GAME.showInfo(GAMEList ,position, color)
  local index, value
  for index, value in ipairs(GAMEList) do
    GAME.ui_container:draw_text(position, 0.05*index, value, color)
  end
  
end

-- 监视各地址,测试时用投入使用时不显示
function GAME.watchAddrs()
  local addrArr, strArr
  addrArr = {
    GAME.POW_REMAIN_ADDR_LOW, GAME.POW_REMAIN_ADDR_HIGH
  }
  strArr = myMame.readAddrArr(addrArr)
  myMame.showInfo(strArr)
end


-- 显示监视的数据
function GAME.check()
  local power, powerRemain, str
  power = GAME.mem:read_u8(GAME.POW_ADDR)
  powerRemain = GAME.mem:read_u8(GAME.POW_REMAIN_ADDR_HIGH)*0x100
  powerRemain = powerRemain+GAME.mem:read_u8(GAME.POW_REMAIN_ADDR_LOW)
  
  str = string.format("P=%d...%03d", power, powerRemain)
  --GAME.container:draw_text(0, 0, "P="..power, 0xFF005500, 0xFFcccccc)
  GAME.container:draw_text(0, 0, str, 0xFF005500, 0xFFcccccc)
  --GAME.ui_container:draw_text(0.05, 0.05, manager.machine.screens[":screen"]:frame_number()) 
  --[[
  GAME.showInfo({
    GAME.mem:read_u8(GAME.TEST_PARAMS[1]),
    GAME.mem:read_u8(GAME.TEST_PARAMS[1]+1),
    GAME.mem:read_u8(GAME.TEST_PARAMS[1]+2),
    GAME.mem:read_u8(GAME.TEST_PARAMS[1]+3),
  } ,0.1, 0xFFffffff)
  ]]
end

-- 显示十字线
function GAME.showCross(xleft, xright, ydown, yup)
  local color
  if (GAME.mem:read_u8(GAME.POW_ADDR) < 3) then
    color = GAME.SIDE_LINE_COLOR
  else
    color = GAME.SIDE_LINE_FULL_COLOR
  end
  
  GAME.container:draw_line(0.0, xleft, 1.0, xleft, color)
  GAME.container:draw_line(0.0, xright, 1.0, xright, color)
  GAME.container:draw_line(yup, 0.0, yup, 1.0, color)
  GAME.container:draw_line(ydown, 0.0, ydown, 1.0, color)
  
end

-- 显示中心方块,x,y是浮点数坐标
function GAME.showSqua(xleft, xright, ydown, yup)

  GAME.container:draw_box(
    ydown, xleft, 
    yup, xright, 
    GAME.BOX_BORDER_COLOR, GAME.BOX_COLOR
  )
  
end

-- 主函数,绑定后每一帧执行一次该函数
function GAME.Main()
  local xleft, xright, ydown, yup
  
  -- 检查是否能执行
  if (not GAME.refreshDevices()) then
    return
  end
  if GAME.ERROR_FLAG then
    return
  end
  
  xleft, xright = GAME.getX()
  ydown, yup = GAME.getY()
  
  -- 显示当前坐标
  --GAME.showInfo({xlow}, 0.05, 0xFF00ff00)
  --GAME.showInfo({yhigh, ylow}, 0.15, 0xFF0000ff)
  -- 监视内存
  --GAME.watchAddrs()
  -- 显示数据监视
  GAME.check()
  -- 完成任务
  --GAME.doMissions()
  if ((xleft < 0.001) or (xright < 0.001) or (yup < 0.001) or (ydown < 0.001)) then
    return
  end
  if ((math.abs(xright-xleft) > 0.1) or (math.abs(yup-ydown) > 0.1)) then
    return
  end
  -- 显示十字线
  GAME.showCross(xleft, xright, ydown, yup)
  -- 显示角色中心方块
  GAME.showSqua(xleft, xright, ydown, yup)
end

--[[ 调试时绑定此函数,确认无错误后可绑定main提高运行效率,使用防错MyMame模块后不需要本函数
function GAME.debug()
  local success, err = pcall(GAME.main)
  if not success then
    print("捕获到错误：", err)
    GAME.ERROR_FLAG = true
  end
end
]]

-- [[运行]]
-- 避免重复加载
if ((_G.game) and (_G.game.NAME) and (_G.game.NAME == GAME.NAME)) then
  print("script already loaded!")
  return _G.game
else
  print("script load: "..GAME.NAME)
end

-- 刷新api接口
GAME.refreshDevices()
-- 绑定每一帧执行的函数,测试时绑定到debug,使用时绑定到main
--emu.register_frame_done(GAME.main, "frame")
--emu.register_frame_done(GAME.debug, "frame")
--myMame.debugRunPerFrame(GAME.Main, 'gunbird')
myMame.runPerFrame(GAME.Main, 'gunbird')
-- 导出到全局变量
_G.game = GAME

return GAME
